using Android.Views;
using Android.Widget;
using Android.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Controls;
using MauiApp.Views.Controls;

namespace MauiApp.Handlers
{
    public class ZoomableImageHandler : ImageHandler
    {
        private ScaleGestureDetector? _scaleDetector;
        private GestureDetector? _gestureDetector;
        private float _scale = 1f;
        private float _fitScale = 1f; // AspectFit baseline
        private float _minScale = 1f;
        private float _maxScale = 6f;
        private float _lastFocusX, _lastFocusY;
        private bool _matrixInitialized;

        public static new IPropertyMapper<Image, ZoomableImageHandler> Mapper =
            new PropertyMapper<Image, ZoomableImageHandler>(ImageHandler.Mapper)
            {
                [nameof(Image.Aspect)] = MapAspect,
            };

        public ZoomableImageHandler() : base(Mapper) { }

        private static void MapAspect(ZoomableImageHandler handler, Image view)
        {
            handler.PlatformView?.SetScaleType(ImageView.ScaleType.Matrix);
        }

        protected override ImageView CreatePlatformView()
        {
            var img = base.CreatePlatformView();
            img.SetScaleType(ImageView.ScaleType.Matrix);
            img.Focusable = true;
            img.FocusableInTouchMode = true;
            img.Clickable = true;
            return img;
        }

        protected override void ConnectHandler(ImageView platformView)
        {
            base.ConnectHandler(platformView);

            _matrixInitialized = false;
            if (VirtualView is ZoomableImage zi)
            {
                _minScale = (float)zi.MinZoom;
                _maxScale = (float)zi.MaxZoom;
            }

            _scaleDetector = new ScaleGestureDetector(Context, new ScaleListener(this));
            _gestureDetector = new GestureDetector(Context, new TapPanListener(this));

            platformView.Touch += OnTouch;
            platformView.LayoutChange += OnLayoutChange;

            platformView.Post(EnsureInitialMatrix);
            platformView.PostDelayed(EnsureInitialMatrix, 16);
            platformView.PostDelayed(EnsureInitialMatrix, 32);
            platformView.PostDelayed(EnsureInitialMatrix, 64);
        }

        protected override void DisconnectHandler(ImageView platformView)
        {
            platformView.Touch -= OnTouch;
            platformView.LayoutChange -= OnLayoutChange;
            _scaleDetector?.Dispose();
            _gestureDetector?.Dispose();
            base.DisconnectHandler(platformView);
        }

        private void OnLayoutChange(object? sender, Android.Views.View.LayoutChangeEventArgs e)
        {
            EnsureInitialMatrix();
        }

        private void EnsureInitialMatrix()
        {
            if (PlatformView?.Drawable == null)
                return;

            var d = PlatformView.Drawable;
            int vw = PlatformView.Width;
            int vh = PlatformView.Height;
            if (vw <= 0 || vh <= 0 || d.IntrinsicWidth <= 0 || d.IntrinsicHeight <= 0)
                return;

            var iw = d.IntrinsicWidth;
            var ih = d.IntrinsicHeight;

            // AspectFit initialization
            float scale = System.Math.Min((float)vw / iw, (float)vh / ih);
            float dx = (vw - iw * scale) * 0.5f;
            float dy = (vh - ih * scale) * 0.5f;

            var m = new Matrix();
            m.PostScale(scale, scale);
            m.PostTranslate(dx, dy);

            PlatformView.ImageMatrix = m;
            PlatformView.Invalidate();

            _fitScale = scale;
            _scale = scale;
            _matrixInitialized = true;
        }

        private void OnTouch(object? sender, Android.Views.View.TouchEventArgs e)
        {
            if (PlatformView == null || PlatformView.Handle == System.IntPtr.Zero)
                return;

            if (!_matrixInitialized)
                EnsureInitialMatrix();

            _scaleDetector?.OnTouchEvent(e.Event);
            _gestureDetector?.OnTouchEvent(e.Event);

            if (e.Event.Action == MotionEventActions.Up || e.Event.Action == MotionEventActions.Cancel)
            {
                ClampTranslation();
            }

            e.Handled = true;
        }

        private void ApplyMatrix(float focusX, float focusY, float scaleFactor, bool accumulateScale)
        {
            if (PlatformView?.Drawable == null || PlatformView.Handle == System.IntPtr.Zero) return;

            var matrix = new Matrix(PlatformView.ImageMatrix);

            if (accumulateScale)
            {
                // read current scale
                float[] vals = new float[9];
                matrix.GetValues(vals);
                var currentScale = vals[(int)Matrix.MscaleX];
                var newScale = currentScale * scaleFactor;
                // Clamp lower bound to fit scale so user can always go back to full-image preview
                var minAllowed = System.Math.Min(_minScale, _fitScale);
                if (newScale < minAllowed) newScale = (float)minAllowed;
                if (newScale > _maxScale) newScale = _maxScale;
                scaleFactor = newScale / currentScale;
                _scale = newScale;
            }

            matrix.PostScale(scaleFactor, scaleFactor, focusX, focusY);

            PlatformView.ImageMatrix = matrix;
            PlatformView.Invalidate();

            // Keep centered while scaling when content becomes smaller than the view
            ClampTranslation();
        }

        private void PanBy(float dx, float dy)
        {
            if (PlatformView?.Drawable == null || PlatformView.Handle == System.IntPtr.Zero) return;
            var matrix = new Matrix(PlatformView.ImageMatrix);
            matrix.PostTranslate(dx, dy);
            PlatformView.ImageMatrix = matrix;
            PlatformView.Invalidate();
        }

        private void ClampTranslation()
        {
            if (PlatformView?.Drawable == null || PlatformView.Handle == System.IntPtr.Zero) return;

            var d = PlatformView.Drawable;
            var vw = PlatformView.Width;
            var vh = PlatformView.Height;

            var matrix = new Matrix(PlatformView.ImageMatrix);
            float[] vals = new float[9];
            matrix.GetValues(vals);
            var scaleX = vals[(int)Matrix.MscaleX];
            var transX = vals[(int)Matrix.MtransX];
            var transY = vals[(int)Matrix.MtransY];

            var iw = d.IntrinsicWidth * scaleX;
            var ih = d.IntrinsicHeight * scaleX; // uniform scale

            float targetX;
            if (iw <= vw)
            {
                // center when smaller
                targetX = (vw - iw) * 0.5f;
            }
            else
            {
                float minX = vw - iw;
                float maxX = 0f;
                if (transX < minX) targetX = minX;
                else if (transX > maxX) targetX = maxX;
                else targetX = transX;
            }

            float targetY;
            if (ih <= vh)
            {
                targetY = (vh - ih) * 0.5f;
            }
            else
            {
                float minY = vh - ih;
                float maxY = 0f;
                if (transY < minY) targetY = minY;
                else if (transY > maxY) targetY = maxY;
                else targetY = transY;
            }

            float dx = targetX - transX;
            float dy = targetY - transY;
            if (dx != 0 || dy != 0)
            {
                matrix.PostTranslate(dx, dy);
                PlatformView.ImageMatrix = matrix;
                PlatformView.Invalidate();
            }
        }

        private void ResetToFit()
        {
            // Recompute and apply aspect-fit matrix; used by double-tap toggle and source change
            EnsureInitialMatrix();
        }

        private class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener
        {
            private readonly ZoomableImageHandler _handler;
            public ScaleListener(ZoomableImageHandler handler) { _handler = handler; }

            public override bool OnScale(ScaleGestureDetector detector)
            {
                _handler._lastFocusX = detector.FocusX;
                _handler._lastFocusY = detector.FocusY;
                _handler.ApplyMatrix(detector.FocusX, detector.FocusY, detector.ScaleFactor, true);
                return true;
            }
        }

        private class TapPanListener : GestureDetector.SimpleOnGestureListener
        {
            private readonly ZoomableImageHandler _handler;
            public TapPanListener(ZoomableImageHandler handler) { _handler = handler; }

            public override bool OnDown(MotionEvent e)
            {
                _handler._lastFocusX = e.GetX();
                _handler._lastFocusY = e.GetY();
                return true;
            }

            public override bool OnScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
            {
                _handler.PanBy(-distanceX, -distanceY);
                return true;
            }

            public override bool OnDoubleTap(MotionEvent e)
            {
                // Toggle between fit and zoomed-in level
                var m = new Matrix(_handler.PlatformView!.ImageMatrix);
                float[] vals = new float[9];
                m.GetValues(vals);
                var current = vals[(int)Matrix.MscaleX];
                var nearFit = System.Math.Abs(current - _handler._fitScale) < 0.01f;

                if (nearFit)
                {
                    // zoom in to DoubleTapZoom around tap
                    if (_handler.VirtualView is ZoomableImage zi)
                    {
                        var target = (float)zi.DoubleTapZoom;
                        // target is absolute scale; compute relative factor from current
                        var rel = target / current;
                        _handler.ApplyMatrix(e.GetX(), e.GetY(), rel, true);
                        _handler.ClampTranslation();
                    }
                }
                else
                {
                    // back to fit
                    _handler.ResetToFit();
                }
                return true;
            }
        }
    }
}
